package com.hwl.bigfileupload.strategy;

import com.hwl.bigfileupload.config.properties.UFOPProperties;
import com.hwl.bigfileupload.dto.FileChunkDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;

/**
 * @author sentry
 * @since 2023-09-23
 */
@Slf4j
@Component
public class LocalStorageUploader {

    public File createTmpFile(String fileName, String savePath) {
        String tempFileName = fileName + "_tmp";
        return new File(savePath, tempFileName);
    }

    /**
     * 分片上传方法
     * 这里使用 RandomAccessFile 方法，也可以使用 MappedByteBuffer 方法上传
     * 可以省去文件合并的过程
     *
     * @param dto            文件参数对象
     * @param ufopProperties 上传配置
     * @param tmpFile        _tmp 文件对象
     * @return true 文件分片全部上传完成
     */
    public boolean sliceUpload(FileChunkDTO dto, UFOPProperties ufopProperties, File tmpFile) {
        // try 自动资源管理
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(tmpFile, "rw")) {
            // 分片大小必须和前端匹配，否则上传会导致文件损坏
            long chunkSize = ufopProperties.readyChunkSize(dto.getChunkSize());
            // 偏移量, 意思是从拿一个位置开始往文件写入，每一片的大小 * 已经存的块数
            long offset = chunkSize * (dto.getChunkNumber() - 1);
            // 定位到该分片的偏移量
            randomAccessFile.seek(offset);
            // 写入该分片数据
            randomAccessFile.write(dto.getFile().getBytes());
            return this.checkUploadStatus(dto, ufopProperties.getSavePath(dto.getIdentifier()));
        } catch (IOException e) {
            log.error("文件分片上传失败：" + e);
            return false;
        }
    }

    private boolean checkUploadStatus(FileChunkDTO dto, String savePath) {
        File confFile = new File(savePath, dto.getFilename() + ".conf");
        try (RandomAccessFile accessConfFile = new RandomAccessFile(confFile, "rw")) {
            // 设置文件长度
            accessConfFile.setLength(dto.getTotalChunks());
            // 设置起始偏移量
            accessConfFile.seek(dto.getChunkNumber() - 1);
            // 将指定的一个字节写入文件中 127，
            accessConfFile.write(Byte.MAX_VALUE);

            dto.setUploadComplete(true);
            byte[] completeStatusList = Files.readAllBytes(confFile.toPath());
            // 创建conf文件文件长度为总分片数，每上传一个分块即向conf文件中写入一个127，那么没上传的位置就是默认的0,已上传的就是127
            for (byte b : completeStatusList) {
                if (b != Byte.MAX_VALUE) {
                    dto.setUploadComplete(false);
                    break;
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return false;
        }

        // 所有分片已经上传完成
        if (dto.getUploadComplete()) {
            confFile.delete();
        }
        return true;
    }

    /**
     * 检查md5是否一致
     */
    public boolean checkFileMd5(File tmpFile, String md5) {
        try (FileInputStream fis = new FileInputStream(tmpFile)) {
            String checkMd5 = DigestUtils.md5DigestAsHex(fis);
            return checkMd5.equals(md5);
        } catch (Exception e) {
            log.error("check file md5 error:" + e);
            throw new RuntimeException("校验md5值时发生错误");
        }
    }

    public void cleanUp(File tmpFile) {
        if (tmpFile.exists()) {
            tmpFile.delete();
        }
    }

    public void renameFile(File tmpFile, String fileName) {
        // 检查要重命名的文件是否存在，是否是文件
        if (!tmpFile.exists() || tmpFile.isDirectory()) {
            log.info("File does not exist: " + tmpFile.getName());
            return;
        }
        String parent = tmpFile.getParent();
        File newFile = new File(parent + File.separator + fileName);
        // 如果存在, 先删除
        if (newFile.exists()) {
            newFile.delete();
        }
        tmpFile.renameTo(newFile);
    }

}

